/**
 * @license
 * Copyright 2020 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import * as Bytes from './bytes';
import {EciesHkdfKemSender, fromJsonWebKey} from './ecies_hkdf_kem_sender';
import * as EllipticCurves from './elliptic_curves';
import * as Random from './random';

describe('ecies hkdf kem sender test', function() {
  it('encapsulate, always generate random key', async function() {
    const keyPair = await EllipticCurves.generateKeyPair('ECDH', 'P-256');
    const publicKey = await EllipticCurves.exportCryptoKey(keyPair.publicKey!);
    const sender = await fromJsonWebKey(publicKey);
    const keySizeInBytes = 32;
    const pointFormat = EllipticCurves.PointFormatType.UNCOMPRESSED;
    const hkdfHash = 'SHA-256';
    const hkdfInfo = Random.randBytes(32);
    const hkdfSalt = Random.randBytes(32);
    const keys = new Set();
    const tokens = new Set();
    for (let i = 0; i < 20; i++) {
      const kemKeyToken = await sender.encapsulate(
          keySizeInBytes, pointFormat, hkdfHash, hkdfInfo, hkdfSalt);
      keys.add(Bytes.toHex(kemKeyToken['key']));
      tokens.add(Bytes.toHex(kemKeyToken['token']));
    }
    expect(keys.size).toBe(20);
    expect(tokens.size).toBe(20);
  });

  it('encapsulate, non integer key size', async function() {
    const keyPair = await EllipticCurves.generateKeyPair('ECDH', 'P-256');
    const publicKey = await EllipticCurves.exportCryptoKey(keyPair.publicKey!);
    const sender = await fromJsonWebKey(publicKey);
    const pointFormat = EllipticCurves.PointFormatType.UNCOMPRESSED;
    const hkdfHash = 'SHA-256';
    const hkdfInfo = Random.randBytes(32);
    const hkdfSalt = Random.randBytes(32);
    try {
      await sender.encapsulate(NaN, pointFormat, hkdfHash, hkdfInfo, hkdfSalt);
      fail('An exception should be thrown.');
      // Preserving old behavior when moving to
      // https://www.typescriptlang.org/tsconfig#useUnknownInCatchVariables
      // tslint:disable-next-line:no-any
    } catch (e: any) {
      expect(e.toString())
          .toBe('InvalidArgumentsException: size must be an integer');
    }
    try {
      await sender.encapsulate(0, pointFormat, hkdfHash, hkdfInfo, hkdfSalt);
      fail('An exception should be thrown.');
      // Preserving old behavior when moving to
      // https://www.typescriptlang.org/tsconfig#useUnknownInCatchVariables
      // tslint:disable-next-line:no-any
    } catch (e: any) {
      expect(e.toString())
          .toBe('InvalidArgumentsException: size must be positive');
    }
  });

  it('new instance, invalid parameters', async function() {
    // Test fromJsonWebKey with public key instead private key.
    const keyPair = await EllipticCurves.generateKeyPair('ECDH', 'P-256');
    const privateKey =
        await EllipticCurves.exportCryptoKey(keyPair.privateKey!);
    try {
      await fromJsonWebKey(privateKey);
      fail('An exception should be thrown.');
    } catch (e) {
    }
  });

  it('new instance, invalid public key', async function() {
    for (const curve
             of [EllipticCurves.CurveType.P256, EllipticCurves.CurveType.P384,
                 EllipticCurves.CurveType.P521]) {
      const crvString = EllipticCurves.curveToString(curve);
      const keyPair = await EllipticCurves.generateKeyPair('ECDH', crvString);
      const publicJwk =
          await EllipticCurves.exportCryptoKey(keyPair.publicKey!);
      // Change the 'x' value to make the public key invalid. Either getting new
      // recipient with corrupted public key or trying to encapsulate with this
      // recipient should fail.
      const xLength = EllipticCurves.fieldSizeInBytes(curve);
      publicJwk['x'] =
          Bytes.toBase64(new Uint8Array(xLength), /* opt_webSafe = */ true);
      const hkdfInfo = Random.randBytes(10);
      const salt = Random.randBytes(8);
      try {
        const sender = await fromJsonWebKey(publicJwk);
        await sender.encapsulate(
            /* keySizeInBytes = */ 32,
            EllipticCurves.PointFormatType.UNCOMPRESSED,
            /* hkdfHash = */ 'SHA-256', hkdfInfo, salt);
        fail('Should throw an exception.');
      } catch (e) {
      }
    }
  });

  it('constructor, invalid parameters', async function() {
    // Test constructor with public key instead private key.
    const keyPair = await EllipticCurves.generateKeyPair('ECDH', 'P-256');
    try {
      new EciesHkdfKemSender(keyPair.privateKey!);
      fail('An exception should be thrown.');
      // Preserving old behavior when moving to
      // https://www.typescriptlang.org/tsconfig#useUnknownInCatchVariables
      // tslint:disable-next-line:no-any
    } catch (e: any) {
      expect(e.toString())
          .toBe('SecurityException: Expected Crypto key of type: public.');
    }
  });
});
